{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 使用PyTorch实现波士顿房价预测及可视化",
   "id": "b5c4d1b293c70bc8"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "from sklearn.metrics import r2_score, mean_squared_error\n",
    "from torch.utils.data import TensorDataset, DataLoader"
   ],
   "id": "25ef14f9df8e53ae",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "# 设置随机种子保证可重复性\n",
    "torch.manual_seed(42)\n",
    "np.random.seed(42)"
   ],
   "id": "15704a6743ee3100",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 1. 数据准备",
   "id": "bc54fd1af16fecb8"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "# 设置随机种子保证可重复性\n",
    "torch.manual_seed(42)\n",
    "np.random.seed(42)"
   ],
   "id": "9caad8ef219c6069",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "# 定义列名（根据波士顿房价数据集的描述）\n",
    "column_names = [\n",
    "    'CRIM',  # 城镇人均犯罪率\n",
    "    'ZN',  # 住宅用地比例\n",
    "    'INDUS',  # 非零售商业用地比例\n",
    "    'CHAS',  # 是否靠近查尔斯河(1=是，0=否)\n",
    "    'NOX',  # 氮氧化物浓度\n",
    "    'RM',  # 住宅平均房间数\n",
    "    'AGE',  # 1940年前建成的自住房屋比例\n",
    "    'DIS',  # 到波士顿五个就业中心的加权距离\n",
    "    'RAD',  # 径向公路可达性指数\n",
    "    'TAX',  # 每万美元财产税率\n",
    "    'PTRATIO',  # 城镇师生比例\n",
    "    'B',  # 黑人比例\n",
    "    'LSTAT',  # 低收入人口比例\n",
    "    'MEDV'  # 自住房屋中位数价格(目标变量)\n",
    "]\n",
    "\n",
    "# 读取数据并指定列名\n",
    "data = pd.read_csv('../data/波士顿房价数据集/housing.data.txt',\n",
    "                   header=None,  # 确保不将第一行误认为表头\n",
    "                   sep='\\s+',  # 按空格/制表符分隔（波士顿数据集是空格分隔）\n",
    "                   names=column_names)  # 传入列名\n",
    "\n"
   ],
   "id": "139708ea904d4d3f",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 2. 数据预处理",
   "id": "e53d5fb72bc3d64e"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "X = data.drop('MEDV', axis=1).values\n",
    "y = data['MEDV'].values.reshape(-1, 1)\n",
    "\n",
    "# 数据标准化\n",
    "scaler = StandardScaler()\n",
    "X_scaled = scaler.fit_transform(X)\n",
    "\n",
    "# 划分训练集和测试集\n",
    "X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)"
   ],
   "id": "e655adbb67d65aeb",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 3. 创建pytorch数据集和数据加载器",
   "id": "a385d3fdc2b211dc"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "batch_size = 32\n",
    "\n",
    "# 转换为pytorch张量\n",
    "train_dataset = TensorDataset(\n",
    "    torch.FloatTensor(X_train),\n",
    "    torch.FloatTensor(y_train)\n",
    ")\n",
    "test_dataset = TensorDataset(\n",
    "    torch.FloatTensor(X_test),\n",
    "    torch.FloatTensor(y_test)\n",
    ")\n",
    "\n",
    "# 创建数据加载器\n",
    "train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n",
    "test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)"
   ],
   "id": "db4152861c9d41dc",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 4. 定义神经网络模型",
   "id": "5e32f0284fde5df5"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "class BostonHousePricePredictor(nn.Module):\n",
    "    def __init__(self, input_dim):\n",
    "        super().__init__()\n",
    "        self.network = nn.Sequential(\n",
    "            nn.Linear(input_dim, 64),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(64, 32),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(32, 16),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(16, 1)\n",
    "        )\n",
    "\n",
    "    def forward(self, x):\n",
    "        return self.network(x)\n",
    "\n",
    "\n",
    "# 初始化模型\n",
    "input_dim = X_train.shape[1]\n",
    "model = BostonHousePricePredictor(input_dim)"
   ],
   "id": "49a5cb6b85087b58",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 5. 定义损失函数和优化器",
   "id": "df8a84fe6aa74c00"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "criterion = nn.MSELoss()\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)"
   ],
   "id": "f2f4d9395ffd8851",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 6. 模型训练和测试",
   "id": "8973ab5d3c7ce5ca"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "epochs = 10\n",
    "train_losses = []\n",
    "test_losses = []\n",
    "\n",
    "for epoch in range(epochs):\n",
    "    print(f\"第 {epoch + 1} 轮训练开始...\")\n",
    "    model.train()  # 训练模式\n",
    "    running_train_loss = 0.0\n",
    "\n",
    "    for inputs, targets in train_loader:\n",
    "        outputs = model(inputs)  # 前向传播\n",
    "        loss = criterion(outputs, targets)  # 计算损失\n",
    "\n",
    "        # 反向传播和优化\n",
    "        optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "\n",
    "        running_train_loss += loss.item() * inputs.size(0)\n",
    "    print(f\"第 {epoch + 1} 轮训练完成，损失: {running_train_loss / len(train_loader.dataset):.4f}\")\n",
    "\n",
    "    # 计算平均训练损失\n",
    "    train_loss = running_train_loss / len(train_loader.dataset)\n",
    "    train_losses.append(train_loss)\n",
    "\n",
    "    # 评估模式\n",
    "    print(f\"第 {epoch + 1} 轮评估开始...\")\n",
    "    model.eval()\n",
    "    running_test_loss = 0.0\n",
    "\n",
    "    with torch.no_grad():\n",
    "        for inputs, targets in test_loader:\n",
    "            outputs = model(inputs)\n",
    "            test_loss = criterion(outputs, targets)\n",
    "            running_test_loss += test_loss.item() * inputs.size(0)\n",
    "\n",
    "    # 计算平均测试损失\n",
    "    test_loss = running_test_loss / len(test_loader.dataset)\n",
    "    test_losses.append(test_loss)\n",
    "    print(f\"第{epoch+1}个epoch的测试损失为：{test_loss:.4f}\")\n",
    "\n"
   ],
   "id": "afc256cb35f44a51",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 7. 评估模型",
   "id": "fced3bdec42d6a61"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "def evaluate_model(loader):\n",
    "    model.eval()\n",
    "    all_targets = []\n",
    "    all_preds = []\n",
    "\n",
    "    with torch.no_grad():\n",
    "        for inputs, targets in loader:\n",
    "            outputs = model(inputs)\n",
    "            all_targets.extend(targets.numpy())\n",
    "            all_preds.extend(outputs.numpy())\n",
    "\n",
    "    return np.array(all_targets), np.array(all_preds)\n",
    "\n",
    "# 训练集评估\n",
    "y_train_true, y_train_pred = evaluate_model(train_loader)\n",
    "train_r2 = r2_score(y_train_true, y_train_pred)\n",
    "train_mse = mean_squared_error(y_train_true, y_train_pred)\n",
    "\n",
    "# 测试集评估\n",
    "y_test_true, y_test_pred = evaluate_model(test_loader)\n",
    "test_r2 = r2_score(y_test_true, y_test_pred)\n",
    "test_mse = mean_squared_error(y_test_true, y_test_pred)\n",
    "\n",
    "print('\\n模型评估:')\n",
    "print(f'训练集 R²: {train_r2:.4f}, MSE: {train_mse:.4f}')\n",
    "print(f'测试集 R²: {test_r2:.4f}, MSE: {test_mse:.4f}')"
   ],
   "id": "90d9560e7f39b504",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 8. 绘制训练和测试损失曲线",
   "id": "6ded7ed2189ef6eb"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "# 8.1 训练和测试损失曲线\n",
    "plt.subplot(2, 2, 1)\n",
    "plt.plot(train_losses, label='Train Loss')\n",
    "plt.plot(test_losses, label='Test Loss')\n",
    "plt.title('Training and Test Loss')\n",
    "plt.xlabel('Epoch')\n",
    "plt.ylabel('Loss')\n",
    "plt.legend()\n",
    "\n",
    "# 8.2 预测值与真实值对比 (训练集)\n",
    "plt.subplot(2, 2, 2)\n",
    "plt.scatter(y_train_true, y_train_pred, alpha=0.5)\n",
    "plt.plot([y_train_true.min(), y_train_true.max()],\n",
    "         [y_train_true.min(), y_train_true.max()], 'r--')\n",
    "plt.title('Train: True vs Predicted Prices')\n",
    "plt.xlabel('True Prices ($1000s)')\n",
    "plt.ylabel('Predicted Prices ($1000s)')\n",
    "\n",
    "# 8.3 预测值与真实值对比 (测试集)\n",
    "plt.subplot(2, 2, 3)\n",
    "plt.scatter(y_test_true, y_test_pred, alpha=0.5)\n",
    "plt.plot([y_test_true.min(), y_test_true.max()],\n",
    "         [y_test_true.min(), y_test_true.max()], 'r--')\n",
    "plt.title('Test: True vs Predicted Prices')\n",
    "plt.xlabel('True Prices ($1000s)')\n",
    "plt.ylabel('Predicted Prices ($1000s)')\n",
    "\n",
    "# 8.4 误差分布\n",
    "plt.subplot(2, 2, 4)\n",
    "errors = y_test_true - y_test_pred\n",
    "plt.hist(errors, bins=30, edgecolor='black')\n",
    "plt.title('Error Distribution')\n",
    "plt.xlabel('Prediction Error ($1000s)')\n",
    "plt.ylabel('Frequency')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ],
   "id": "cb4c808fea143d4a",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 9. 特征重要性分析",
   "id": "5aae208757e22c3b"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "weights = model.network[0].weight.data.abs().mean(dim=0).numpy()\n",
    "feature_importance = pd.DataFrame({\n",
    "    'Feature': column_names[:-1],\n",
    "    'Importance': weights\n",
    "}).sort_values('Importance', ascending=False)\n",
    "\n",
    "plt.figure(figsize=(10, 6))\n",
    "plt.barh(feature_importance['Feature'], feature_importance['Importance'])\n",
    "plt.title('Feature Importance')\n",
    "plt.xlabel('Importance')\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ],
   "id": "2fd59f14ddc97b04",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": "",
   "id": "257c1c0e91853b9c",
   "outputs": [],
   "execution_count": null
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
